#include "precompiled/pchkrayserkraw.h"
#include "kraw/KrawD3D9MeshMaterial.h"
#include "kraw/kraw3dmodel.h"

#include "kernel/nkernelserver.h"
#include "kernel/ncmdprotonativecpp.h"

#include <d3dx9.h>

//-----------------------------------------------------------------------------

extern LPDIRECT3DDEVICE9 g_pd3d9Device;

//structure for exported data, ready to load vertex buffer
struct EXPORTHEADER
{
    uint vertexSize;
    uint nVertices;
    uint nIndices;
};

//-----------------------------------------------------------------------------

class KrawD3D9Model
{
    KrawD3D9Model();

    ~KrawD3D9Model();

    HRESULT LoadVertexBuffer( uint nVertices, uint vertexSize, uint vertexFormat, void* pData );

};

//-----------------------------------------------------------------------------

void KrawD3D9MeshMaterial::DeclareStream( uint type, uint size )
{
    if ( type != Invalid )
    {
        Stream& stream = this->streams.At( this->streams.Size() );
        stream.type = type;
        stream.size = size;
    }
}

//-----------------------------------------------------------------------------

KrawD3D9MeshMaterial::KrawD3D9MeshMaterial():
    pVertexDecl(0)
{
}

//-----------------------------------------------------------------------------

KrawD3D9MeshMaterial::~KrawD3D9MeshMaterial()
{
    this->Destroy();
}

//-----------------------------------------------------------------------------

bool KrawD3D9MeshMaterial::Init()
{
    if ( !this->InitVertexDeclaration() ) return false;
/*
    //create vertex declaration
    IDirect3DVertexDeclaration9* d3d9vdecl;
    //TODO- look for a compatible vertex declaration
    //if ( !FindVertexDeclaration( this->pVertexDecl ) )

    D3DVERTEXELEMENT9* vertexdecl = (D3DVERTEXELEMENT9*) this->pVertexDecl;
    if ( SUCCEEDED( g_pd3d9Device->CreateVertexDeclaration( vertexdecl, &d3d9vdecl ) ) )
    {
        this->vertexDecl = d3d9vdecl;
        return true;
    }
*/
    return Kraw3DMaterial::Init();
}

//-----------------------------------------------------------------------------

void KrawD3D9MeshMaterial::Destroy()
{
    if ( this->pVertexDecl )
    {
        n_delete_array( this->pVertexDecl );
        this->pVertexDecl = 0;
    }
}

//-----------------------------------------------------------------------------

bool KrawD3D9MeshMaterial::InitVertexDeclaration()
{
    if ( this->streams.Empty() ) return false;

    D3DVERTEXELEMENT9* decl = n_new_array( D3DVERTEXELEMENT9, MaxStreams );
    int curOffset  = 0;
    int curIndex = 0;
    for ( int index = 0; index < this->streams.Size(); ++index )
    {
        Stream& stream( this->streams[index] );

        switch ( stream.size )
        {
        case 1:
            decl[curIndex].Type = D3DDECLTYPE_FLOAT1;
            break;
        case 2:
            decl[curIndex].Type = D3DDECLTYPE_FLOAT2;
            break;
        case 3:
            decl[curIndex].Type = D3DDECLTYPE_FLOAT3;
            break;
        case 4:
            decl[curIndex].Type = D3DDECLTYPE_FLOAT4;
            break;
        default:
            continue;
        }

        decl[curIndex].Stream = 0;
        decl[curIndex].Offset = static_cast<WORD>( curOffset );
        decl[curIndex].Method = D3DDECLMETHOD_DEFAULT;

        switch ( stream.type )
        {
            case Position:
                decl[curIndex].Usage      = D3DDECLUSAGE_POSITION;
                decl[curIndex].UsageIndex = 0;
                break;
            case Normal:
                decl[curIndex].Usage      = D3DDECLUSAGE_NORMAL;
                decl[curIndex].UsageIndex = 0;
                break;
            case Tangent:
                decl[curIndex].Usage      = D3DDECLUSAGE_TANGENT;
                decl[curIndex].UsageIndex = 0;
                break;
            case Binormal:
                decl[curIndex].Usage      = D3DDECLUSAGE_BINORMAL;
                decl[curIndex].UsageIndex = 0;
                break;
            case Color:
                decl[curIndex].Usage      = D3DDECLUSAGE_COLOR;
                decl[curIndex].UsageIndex = 0;
                break;
            case TexCoord0:
                decl[curIndex].Usage      = D3DDECLUSAGE_TEXCOORD;
                decl[curIndex].UsageIndex = 0;
                break;
            case TexCoord1:
                decl[curIndex].Usage      = D3DDECLUSAGE_TEXCOORD;
                decl[curIndex].UsageIndex = 1;
                break;
            case TexCoord2:
                decl[curIndex].Usage      = D3DDECLUSAGE_TEXCOORD;
                decl[curIndex].UsageIndex = 2;
                break;
            case TexCoord3:
                decl[curIndex].Usage      = D3DDECLUSAGE_TEXCOORD;
                decl[curIndex].UsageIndex = 3;
                break;
            case Weights:
                decl[curIndex].Usage      = D3DDECLUSAGE_BLENDWEIGHT;
                decl[curIndex].UsageIndex = 0;
                break;
            case JIndices:
                decl[curIndex].Usage      = D3DDECLUSAGE_BLENDINDICES;
                decl[curIndex].UsageIndex = 0;
                break;
        }

        curOffset += stream.size;
        curIndex++;
    }

    //add vertex declaration terminator
    decl[curIndex].Stream = 0xff;
    decl[curIndex].Offset = 0;
    decl[curIndex].Type   = D3DDECLTYPE_UNUSED;
    decl[curIndex].Method = 0;
    decl[curIndex].Usage  = 0;
    decl[curIndex].UsageIndex = 0;

    this->pVertexDecl = (void*) decl;

    return true;
}

//-----------------------------------------------------------------------------

bool KrawD3D9MeshMaterial::ExportVertices( Kraw3DModel* pModel, uint& vertexSize, float*& pVertexData )
{
    if ( !pModel->IsValid() ) return false;

    //compute vertex size (in bytes)
    uint nFloats = 0;
    for ( int index = 0; index < this->streams.Size(); ++index )
        nFloats += this->streams[index].size;

    vertexSize = nFloats * sizeof(float);
    pVertexData = (float*) n_new_array( BYTE, pModel->nVertices * vertexSize );

    //convert vertex data
    BYTE* pBufferDst = (BYTE*) pVertexData;
    float* pVertexSrc = 0;
    for ( int index = 0; index < this->streams.Size(); ++index )
    {
        bool found = false;
        switch ( this->streams[index].type )
        {
        case Position:
            found = pModel->GetStreamData( Kraw3DModel::Coord, &pVertexSrc );
            break;
        case Color:
            found = pModel->GetStreamData( Kraw3DModel::Diffuse, &pVertexSrc );
            break;
        case Normal:
            found = pModel->GetStreamData( Kraw3DModel::Normal, &pVertexSrc );
            break;
        default:
            continue;
        }

        FLOAT* pVertexDst = (FLOAT*) pBufferDst;
        uint stride = this->streams[index].size * sizeof(float);
        pBufferDst += stride;

        //copy relevant vertex data
        if ( found )
        {
            for ( uint iVertex = 0; iVertex < pModel->nVertices; ++iVertex )
            {
                memcpy( pVertexDst, pVertexSrc, stride );
                pVertexDst += nFloats;
                pVertexSrc += pModel->nComponents;
            }
        }
    }

    return true;
}

//-----------------------------------------------------------------------------

bool KrawD3D9MeshMaterial::ExportIndices( Kraw3DModel* pModel, uint*& pIndexData )
{
    if ( !pModel->IsValid() ) return false;

    //TODO- support 16-bit indices if they are enough
    memcpy( pIndexData, pModel->pIndices, pModel->nIndices * sizeof(DWORD) );

    return true;
}

//-----------------------------------------------------------------------------

void KrawD3D9MeshMaterial::Delete( float* pVertexData, uint* pIndexData )
{
    if ( pVertexData )
        n_delete_array( pVertexData );
    if ( pIndexData )
        n_delete_array( pIndexData );
}

//-----------------------------------------------------------------------------

void* KrawD3D9MeshMaterial::Load( Kraw3DModel* pModel )
{
    n_assert( pModel );
    n_assert( pModel->IsValid() );

    float* pVertexData;
    uint vertexSize;
    if ( !this->ExportVertices( pModel, vertexSize, pVertexData ) )
        return 0;

    //TODO- handle 16-bit indices

    //create a mesh from the exported data
    ID3DXMesh* pMesh = NULL;
    if ( FAILED( D3DXCreateMesh( pModel->nIndices / 3, pModel->nVertices,
                                 D3DXMESH_MANAGED | D3DXMESH_32BIT,
                                 (D3DVERTEXELEMENT9*) this->pVertexDecl,
                                 g_pd3d9Device, &pMesh ) ) )
        return false;

    //copy vertex data
    float* pVertex;
    if ( FAILED( pMesh->LockVertexBuffer( 0, (void**) &pVertex ) ) )
        return false;
    memcpy( pVertex, pVertexData, pModel->nVertices * vertexSize );
    pMesh->UnlockVertexBuffer();

    //copy index data
    DWORD* pIndex;
    if ( FAILED( pMesh->LockIndexBuffer( 0, (void**) &pIndex ) ) ) 
        return false;
    memcpy( pIndex, pModel->pIndices, pModel->nIndices * sizeof(DWORD) );
    pMesh->UnlockIndexBuffer();

    //TODO- copy attribute data
    DWORD* pSubset;
    if ( FAILED( pMesh->LockAttributeBuffer( 0, &pSubset ) ) )
        return 0;
    //memcpy( pSubset, m_Attributes.GetData(), m_Attributes.GetSize() * sizeof(DWORD) );
    memset( pSubset, 0, (pModel->nIndices / 3) * sizeof(DWORD) );
    pMesh->UnlockAttributeBuffer();

    return (void*) pMesh;
}

//-----------------------------------------------------------------------------

void KrawD3D9MeshMaterial::Unload( void* /*pModelData*/ )
{
    //TODO- fix crash
//    ID3DXMesh* pMesh = (ID3DXMesh*) pModelData;
//    pMesh->Release();
}

//-----------------------------------------------------------------------------

void KrawD3D9MeshMaterial::UpdateTransform()
{
    D3DXMATRIXA16* matWorld = (D3DXMATRIXA16*) &this->mWorld;
    g_pd3d9Device->SetTransform( D3DTS_WORLD, matWorld );
}

//-----------------------------------------------------------------------------

void KrawD3D9MeshMaterial::Render( void* pModelData )
{
    //update world transform
    this->UpdateTransform();

    //this goes between calls to effect->Begin(), effect->End(), effect->BeginPass(), effect->EndPass();
    ID3DXMesh* pMesh = (ID3DXMesh*) pModelData;
    pMesh->DrawSubset( 0 );
}
